๐ ๊ณผ์ ์ค๋ช
์ด๋ฒ ๊ณผ์ ๋ ๊ฐ๋จํ๊ฒ ํ๋์ ํ์ด์ง๋ก ๋์ด์๊ณ ๊ฒ์์ฐฝ๊ณผ ๊ฒ์์ด ์ถ์ฒ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ๊ณผ์ ์๋ค. ๊ฒ์์ฐฝ์ UI๋ก ๊ตฌํํ ํ์ ๊ฒ์์ฐฝ์ ์ ๋ ฅ์ ๋ฐ๋ผ ๊ด๋ จ๋ ์ถ์ฒ ๊ฒ์์ด๋ฅผ api๋ฅผ ์ด์ฉํด ๋ฐ์์ ๋ณด์ฌ์ฃผ๋ฉด ๋๋ ๊ฐ๋จํด ๋ณด์๋... ํ์ง๋ง ํ๋ํ๋ ๊ณต๋ถํ ๊ฒ ๋ง์๋ ์ ์ตํ ๊ณผ์ ์๋ค.
์ธ๋ถ ๋ถ๋ถ์ ์ด 4๊ฐ์ง๋ก ๋ค์๊ณผ ๊ฐ์ด ์ ๋ฆฌํ ์ ์๋ค.
- ์ถ์ฒ ๊ฒ์์ด์์ ํค์๋ ๋ณผ๋์ฒ๋ฆฌ
- APIํธ์ถํ ๊ฒฐ๊ณผ๋ฅผ ๋ก์ปฌ ์บ์ฑ
- ์ ๋ ฅ๋ง๋ค apiํธ์ถ์ ํ์ง ์๊ฒ api ํธ์ถ ํ์๋ฅผ ์ค์ด๊ธฐ
- ํค๋ณด๋ ๋ง์ผ๋ก ์ถ์ฒ ๊ฒ์์ด๋ก ์ด๋ํ๊ฒ ํ๊ธฐ
์ ๋ฒ๊ณผ ๋ฌ๋ฆฌ React Query๋ฅผ ์ฌ์ฉํ์ง ์์๋๋ฐ, ์ด์ ๋ React Query๋ฅผ ์ฌ์ฉํ๋ฉด ์๋์ผ๋ก ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ก์ปฌ ์บ์ฑํ ์ ์๊ธฐ ๋๋ฌธ์ ์ ํ๋์๊ณ , Typescript๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์งํํ๋ค.
๐ ์ ์ญ์ํ์ ๋ํ ๊ณ ๋ฏผ
ํน๋ณํ ์ด๋ฒ๊ณผ์ ๋ฅผ ํ๋ฉด์ ์ ๊ฒฝ์ผ๋ ๋ถ๋ถ์ ์ ์ญ์ํ์๋ค. ๋น์ฐํ ๊ณ ๋ฏผํด์ผํ ๋ถ๋ถ์ด์ง๋ง ์ ๋ฒ ๊ณผ์ ๋ฅผ ํผ๋๋ฐฑ์ ๋ฐ์ผ๋ฉด์ ์ ์ญ์ํ๋ฅผ ์ด๋ป๊ฒ ๊ด๋ฆฌํด์ผ ํ๋์ง ๋ํ ์ผํ๊ฒ ์๊ฒ ๋์๋ค. ์ ๋ฒ ๊ณผ์ ์์ ๋ง์ง๋ง์ react query๋ฅผ ์ด์ฉํด ๋ฆฌํฉํ ๋ง์ ํ๋ฉด์ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ํ์ ๋ค์ context API๋ฅผ ์ด์ฉํด ์ ์ญ์ํ๋ก ๋๊ฒจ์ฃผ๋ ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ๋ค. ๋ฉํ ๋๊ป์ Server State๋ฅผ ๋ค์ Client State๋ก ๋ค๋ฃฐ ์ด์ ๊ฐ ์๋ค๊ณ ๋ง์ํด์ฃผ์ จ๋ค.
Server State์ Client State, ์ฒ์ ์๊ฐํด๋ณธ ์ฃผ์ ์๋ค. ๋น์ฐํ server์์ ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด client์ ์ ์ญ์ํ๋ก ๋ฐ์์ ์ฌ์ฉํด์ผํ๋ค๊ณ ์๊ฐํ์๋ค. server State์ client State๋ ์ด๋ป๊ฒ ๋ค๋ฅธ๊ฑธ๊น?
Server state์ Client state
๋์ ์ฐจ์ด์ ๋ํด์ ์ฐพ์๋ณด๋ฉด์ React Query์ ์ํ๊ด๋ฆฌ :: 2์ ์ฐ์ํํ ํฌ์ธ๋ฏธ๋ ๊ธ์ ์ฝ์ผ๋ฉด์ ์ดํดํ๊ฒ ๋์๋ค.
Client State
- client์์ ๊ด๋ฆฌํ๋ ์ํ
- ๋ค๋ฅธ ์ฌ๋๊ณผ ๊ณต์ ํ์ง ์๊ณ client ํ๋ฉด์์ ์ฌ์ฉ์์ interaction์ผ๋ก ๋ณํ๊ฐ๋ฅ
- client ์์ ์ต์ ์ผ๋ก ๊ด๋ฆฌ๋จ
Server State
- server์์ ๊ด๋ฆฌํ๋ ์ํ
- fetch์ ๊ฐ์ด ๋คํธ์ํฌ api๊ฐ ํ์ํจ
- ๋ค๋ฅธ ์ฌ๋๊ณผ ๊ณต์ ํ๋ ๋ฐ์ดํฐ๋ก ๋ณ๊ฒฝ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์๊ฐ ์ ์ ์์ ์ ์์
๋์ ๊ฐ์ฅ ํฐ ์ฐจ์ด์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๋ ์ฃผ์ฒด๊ฐ ๋๊ตฌ๋์ ์๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ด์ ๋ฐฉ์์ฒ๋ผ api๋ฅผ ํธ์ถํ ํ์ ๊ฒฐ๊ณผ๋ฅผ ๋ก์ปฌ์ ์ ์ญ์ํ๋ก ๊ด๋ฆฌํ๋ค๋ฉด ์ฌ์ค์ ์๋ฒ์ ๋ฐ์ดํฐ๊ฐ ๋ฐ๋์์ ๋ ๋ฐ์ํ๊ธฐ ์ด๋ ค์ด ๋ฌธ์ ๊ฐ ์กด์ฌํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฐ ๋ถ๋ถ์ ํด๊ฒฐํ๊ธฐ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค ๋ง์ ๋ฐฉ๋ฒ์ด ์์ง๋ง react Query๋ฅผ ์ด์ฉํ๋ฉด stale time์ ๊ฐ์ option๋ค์ ์ด์ฉํด ์๊ฐ์ ์ ํด ์๋ก ๋ฐ์์ฌ ์ ์๊ณ , ์๋ ๋ก์ปฌ cahche๊ธฐ๋ฅ๋ ์ ๊ณตํ๊ธฐ์ Server state์ Client state ๋ฅผ ๊ตฌ๋ถํด์ ๊ด๋ฆฌํ ์ ์๋ค๋ ํฐ ์ฅ์ ์ ๊ฐ๊ฒ๋๋ค. ์ด์ ์ผ react query์ ํ์์ฑ์ ์ ๋๋ก ์ดํดํ ์ ์๊ฒ ๋์๊ณ , ๋ค์ step์ผ๋ก ๊ทธ๋ผ ์ด๋ค๊ฑธ client์ ์ ์ญ์ํ๋ก ๊ด๋ฆฌํด์ผํ ๊น?
์ด๋ค ๊ธฐ์ค์ผ๋ก ์ ์ญ์ํ๋ฅผ ์ ํํ ๊น?
Server state์ Client state ๋ฅผ ๊ตฌ๋ถํ๊ณ ๋๋ ๋ด๊ฐ ์๊ฐํด์๋ ์ ์ญ ์ํ์ ๊ธฐ์ค์ด ํ๋ค๋ ธ๋ค. ์ด๋ค ๊ธฐ์ค์ผ๋ก ์ ์ญ์ํ๋ฅผ ์ ํํ๋ฉด ๋ ์ง๊ฐ ๋ค์ ๊ณ ๋ฏผ์ผ๋ก ์ด์ด์ก๋ค. ํด๋ต์ context API๋ฅผ ์ด์ฉํ๋ ์ด์ ์ ๋์ผํ๋ค, Prop-drilling์ ๋ง๊ธฐ ์ํด. ๋ฐ๋ณต์ ์ผ๋ก ์ํ๋ฅผ ์ ๋ฌํด์ฃผ๋ ๊ฒฝ์ฐ ๋ด ๊ธฐ์ค์ 2๋ฒ ์ด์์ component๋ฅผ ๊ฑฐ์ณ์ ์ ๋ฌํด์ค์ผํ๋ค๋ฉด ํ๋์ client ์ ์ญ์ํ๋ก ๊ด๋ฆฌํ๋ฉด ์ข๊ฒ ๋ค๋ ๊ธฐ์ค์ด ์๊ฒผ๋ค. ๊ทธ๋ฌ๋ฉด ์ ์ญ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ contextAPI, redux, recoil๊ณผ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ค์ผ๊น?
Query String: ์ํ์ง ์ ์ญ์ํ
์ํ๊ด๋ฆฌ๋ผ ํ๋ฉด contextAPI๋ redux์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง์ ๋ ์ฌ๋ ธ์ง๋ง, ์ด๋ฒ ์์ ์๊ฐ์ ํ๋์ ์ ์ญ์ํ ๊ด๋ฆฌ๋ฐฉ๋ฒ์ผ๋ก Query String์ ๋ํด ๋ฉํ ๋๊ป์ ์๋ ค์ฃผ์ จ๋ค. ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์์์ด์ง๋ง ๊ณ ๋ฏผํ์ง ๋ชปํ๋ ๋ถ๋ถ์ด์๋ค. ์ฐ๋ฆฌ๊ฐ ์ผํ๋ชฐ ์ฌ์ดํธ์์ ์ฌ๋ฌ๊ฐ์ง ํํฐ(ํค, ์ท ์ข ๋ฅ ๋ฑ)๋ฅผ ์ ์ฉํ ํ์ ์น๊ตฌ์๊ฒ ๊ณต์ ํ๋ค๊ณ ํ์ ๋ ํ๋ฉด๊ณผ url์ด ๋๊ธฐํ ๋์ด์์ง ์๋ค๋ฉด ์น๊ตฌ๊ฐ ๋ณด๋ ํ๋ฉด์ ๋ด๊ฐ ๋ณด์ฌ์ฃผ๊ณ ์ถ์ดํ๋ ํ์ด์ง์๋ ๋ค๋ฅธ ํ์ด์ง ๋ ๊ฒ์ด๋ค.
์ด๋ ๊ฒ query String์ ํ๋์ ์ ์ญ์ํ๋ก ์๊ฐํด query String์ ๋ฐ๋ผ ํ๋ฉด์ ๋ ๋๋งํ ์ ์๊ณ , ์ด๋ฌํ ๋ฐฉ๋ฒ์ ์ธ๋ถ์ ์ธ ํํฐ๋, ์ ๋ ฅ๊ฐ์ ๊ด๋ฆฌํ ๋ ์ ์ฉํ๋ค๋ ๊ฒ์ ์๊ฒ ๋์๋ค.
(์ค์ ๊ตฌ๊ธ ๊ฒ์์ฐฝ์ ๋ด์ฉ์ ์ ๋ ฅํ ํ์ ๊ตฌ๊ธ์ url์ ๋ณด๋ฉด ๋ฐ์๋์ด ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค)
์ด๋ฒ ๊ณผ์ ๋ฅผ ํ๋ฉด์ ์๋๋ keyword๋ผ๋ ์ ์ญ์ํ๋ฅผ ๋ง๋ค๊ณ input์ ๊ฐ์ด ๋ณํํ ๋๋ง๋ค keyword์ํ๋ฅผ ๋ณํ์์ผ api๋ฅผ ํธ์ถํ๋ ค ํ์ง๋ง, query String์ ๋ฐ๊ฟ์ผ๋ก์จ query์ ๋ฐ๋ผ api๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ผ๋ก ์ํ๊ด๋ฆฌ๋ฅผ ํด๋ณด๋ฉด ์ด๋จ๊น๋ผ๋ ์๊ฐ์ผ๋ก ํ๋ก์ ํธ๋ฅผ ์งํํ๋ค.
์ฒ์์ useParams๋ฅผ ์ด์ฉํ๋ฉด query String์ ๋ฐ์์ฌ ์ ์์๊น ํ์ง๋ง useParms๋ก ๋ฐ์์ฌ ์ ์๋ ๊ฒ์ ๋ง๊ทธ๋๋ก paramter, /:id๋ผ๋ฉด id๊ฐ๋ง ๋ฐ์์ฌ ์ ์์๋ค. query string์ ๋ฐ์์ค๊ธฐ ์ํด์๋ window.location์ ์ด์ฉํ๊ฑฐ๋ react-router-dom์ useLocation์ ์ด์ฉํด์ path๋ฅผ ๋ฐ๊ณ ๋ณ๋์ qs ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํด object๋ก ๋ง๋ ํ์ ๋ฐ์์ค๋ ๋ฐฉ๋ฒ๋ ์์๋ค. ํ์ง๋ง ์ถ๊ฐ์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ด, ๊ฐ๊ฒฐํ๊ฒ ์ฌ์ฉํ๊ณ ์ถ์ด useSearchParam์ด๋ผ๋ react-router-dom์ ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ค.
useSearchParam์ useState์ฒ๋ผ ๋ฐฐ์ด์ ์ฒซ ์์๋ ํ์ฌ url์ ํ๋์ด ๋ด๊ฒจ์๊ณ , ๋๋ฒ์งธ ์์๋ param์ ๋ณ๊ฒฝํ ์ ์๋ setState์ ๊ฐ์ ํจ์๊ฐ ๋ด๊ฒจ์๋ค. ๊ฐ๋ง ๋ฐ์์ค๋ฉด ๋๊ธฐ ๋๋ฌธ์ ์ฒซ์์์ ๋ฉ์๋์ธ get์ผ๋ก ํด๋น query string์ ๋ฐ์ ํ๋์ ์ํ๋ก ๊ด๋ฆฌํ๋ค.
import { useSearchParams } from "react-router-dom"
const useQueryString = () => {
const [params] = useSearchParams()
const query = params.get("q") || ""
return query
}
export { useQueryString }๐พ ๋ก์ปฌ ์บ์ฑ
์ด๋ฒ ๊ณผ์ ์ ํต์ฌ์ ์ธ ๋ถ๋ถ์ค ํ๋์๋ ๋ก์ปฌ ์บ์ฑ์ ํ ์ ์๋ ๋ฐฉ๋ฒ์ผ๋ก HTTP cache-control, local Storage, Session Storage ์ธ๊ฐ์ง์ ๋ํด์ ๊ณ ๋ คํ๊ณ , Session Storage๋ฅผ ์ด์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ค.
HTTP cache-control
HTTP cache-control์ apiํธ์ถ ํ์ ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ํ์ ์ดํ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ์ ๋ ์๋ก api๋ฅผ ํธ์ถํ๋ ๊ฒ์ด ์๋๋ผ, cache๋์ด ์๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ ๋ฐฉ๋ฒ์ผ๋ก, ์๋ฒ state์ ๋ณํ๋ฅผ ํ์ธํ๊ณ ๊ฐ์ ธ์ฌ ์ ์์ด ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ด๋ผ ์๊ฐ๋์๋ค. ๋คํธ์ํฌ ์์์๋ cache data์ ๊ฒฝ์ฐ ๋ฐ์ดํฐ ํฌ๊ธฐ๊ฐ ์๊ณ , ์๋ฒ์ ์๋ต์ด 200๊ณผ 304๋ก ๊ตฌ๋ถ๋์ด ํ์๋์์ง๋ง, client์์์ ์๋ฒ ์๋ต์ด ๋ชจ๋ ๋์ผํ๊ฒ 200 OK๋ก ๋ค์ด์ cache๋ ๋ฐ์ดํฐ์ธ์ง ํ์ธํ๊ธฐ ์ด๋ ค์ด ๋ฌธ์ ๊ฐ ์์๋ค.
[์๋ฒ์ ์๋ต]
[client๊ฐ ๋ฐ์ ์๋ฒ์ ์๋ต]
Browser Storage
๋ค์์ผ๋ก ๋ธ๋ผ์ฐ์ ๋ด์ storage๋ฅผ ์ด์ฉํ๋ ๋ฐฉ์์ ๊ณ ๋ คํ๋ค. ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋ธ๋ผ์ฐ์ ๋ก ๊ฐ์ ธ์์ client์์ ์ ์ฅํ๋ค๋ณด๋ ์๋ฒ์ ๋ฐ์ดํฐ ๋ณํ๋ฅผ ์ ์ ์๋ ๋จ์ ์ด ์กด์ฌํ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ์ ๋ซ์๋ ๊ณ์ํด์ ์ ์ฅํ๋ local storage๊ฐ ์๋๋ผ ๋ธ๋ผ์ฐ์ ๋ฅผ ๋ซ์ผ๋ฉด ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ์ด๊ธฐํํ๊ณ , ์๋ก์ด api ํธ์ถ์ ํ๋ session storage๋ฅผ ์ด์ฉํ๋ ๊ฒ ๋ ์ ์ ํ ์ ํ์ด๋ผ ์๊ฐ๋์๋ค.
session storage๋ฅผ ์ฒ์ ์จ๋ดค์ง๋ง local Stoarge์ ๋์ผํ method๋ผ ์ฌ์ฉ๋ฒ์ ๊ฐ๋จํ๊ณ , ์ ์ฅํ ๋๋ JSON.Stringfy๋ฅผ ๋ฐ์์ฌ ๋๋ JSON.parse๋ฅผ ์ด์ฉํด ๋ณํํด์ค์ผํ๊ธฐ ๋๋ฌธ์ CacheService class๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ๋ค.
export default class CacheService {
static setData = (query: string, words: SearchType[]) => {
const stringifyWord = JSON.stringify(words)
sessionStorage.setItem(query, stringifyWord)
}
static getData = (query: string) => {
const data = sessionStorage.getItem(query)
const parsedData: SearchType[] = JSON.parse(data || JSON.stringify([]))
return parsedData
}
}๋ค๋ฅธ ํ์ ์ฝ๋์์ map์ ์ด์ฉํด ๋ก์ปฌ ์บ์ฑ์ ๊ตฌํํ ๋ถ๋ถ์ ๋ณด๋ฉด์ ์ด๋ ๊ฒ ๊ตฌํํ ์๋ ์์๊ฒ ๊ตฌ๋ ๋๋ผ๊ธฐ๋ ํ๋ค.
const myCache = new Map()
export const setMyCacheData = <T>(key: string, data: T) => {
const value = { data, expired: new Date().getTime() + 5000 }
myCache.set(key, value)
}
export const getMyCacheData = (key: string) => {
if (myCache.has(key)) {
if (myCache.get(key).expired > new Date().getTime()) {
return myCache.get(key).data
} else {
myCache.delete(key)
}
}
return null
}# ๐จํค์๋ Bold ์ฒ๋ฆฌ
ํค์๋๋ queryString์ ์ด์ฉํด์ ๊ฐ๋จํ๊ฒ ๊ฐ์ ธ์ฌ ์ ์์์ง๋ง api ๋ฐ์ดํฐ์์ ํด๋น query๋ฅผ bold์ฒ๋ฆฌํ๊ธฐ ์ํด์๋ ๋ณ๋์ ๋ฐฉ๋ฒ์ ๊ณ ๋ฏผํด์ผํ๋ค. string์ tag๋ก ๋ฐ๊ฟ์ค์ผํ๊ธฐ ๋๋ฌธ์ ๊ด๋ จ ๋ด์ฉ์ ์ฐพ์๋ณด์๋๋ ๊ฐ์ฅ ๋จผ์ ๋์จ๊ฑด dangerouslySetInnerHTML์ด์๋ค. ์ด๋ฆ๋ถํฐ ์ฐ์ง๋ง๋ผ๋ ๊ฒ ๊ฐ์ ์ ์ฌ์ฉํ๋ฉด ์๋๋์ง ๋จผ์ ์ฐพ์๋ณด๋, ๋ฌธ์์ด์ ํ๊ทธ๋ก ๋ฐ๊ฟ ์ ์๋ค๋ ๊ฒ์ ์ฌ์ฉ์๊ฐ ์์๋ก script๋ฅผ ์ฝ์
ํ ์ ์๋ค๋ ๋ณด์๋ฌธ์ ๊ฐ ์กด์ฌํ๋ค.
์ด๋ฅผ ๋ณด์ํ๊ธฐ ์ํด ์ฐ์ ๋ฌธ์์ด ๋ด์ query๋ถ๋ถ์ ๊ธฐ์ค์ผ๋ก split์ผ๋ก array๋ฅผ ๋ง๋ค์ด ํค์๋๊ฐ ์๋ index์๋ง <b></b>๋ก ๊ฐ์ธ์ฃผ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ๋ค. ํค์๋๋ฅผ ๊ธฐ์ค์ผ๋ก ์๋ฅด๊ธฐ ๋๋ฌธ์ ํญ์ ํค์๋๋ ๋๋ฒ์จฐ์ ์กด์ฌํด ์ฝ๊ฒ ์ฒ๋ฆฌํ ์ ์์๋ค.
//splitByKeyword.ts
const splitByKeyword = (query: string, text: string) => {
if (text.toUpperCase().includes(query.trim().toUpperCase())) {
return text.split(new RegExp(`(${query})`, 'gi'));
}
};
export { splitByKeyword };
//searchItem.tsx
const KEYWORD_INDEX = 1;
const SearchItem = ({
...
}: SearchItemProps) => {
const query = useQueryString();
const textArray = splitByKeyword(query, text);
return (
<Link to={`/search?q=${text}`}>
<S.Wrapper ref={itemRef} active={active} onMouseEnter={handleMouseEnter}>
<BsSearch />
<span>
{textArray?.map((item, idx) => {
if (idx === KEYWORD_INDEX) {
return <b key={item}>{item}</b>;
}
return item;
})}
</span>
</S.Wrapper>
</Link>
);
};
export default SearchItem;โจDebouncing๊ณผ Throttling
์ ๋ ฅ๋ง๋ค APIํธ์ถํ์ง ์๋๋ก ํธ์ถ ํ์๋ฅผ ์ค์ด๋ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๋ค๊ฐ Debouncing๊ณผ Throttling์ ๋ํด ์๊ฒ ๋์๋ค. ์ฌ์ค ์ด์ ๋ฌดํ ์คํฌ๋กค์ ๊ตฌํํ๋ ๊ณผ์ ์์ scroll event๋ฅผ ์ด์ฉํด ๊ตฌํํ์ ๋ ์๊ธฐ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ผ๋ก Debouncing๊ณผ Throttling์๋ค๋ ์ ์ ๋ณธ ์ ์ด ์์๋ค. ํ์ง๋ง ๊ทธ๋๋ Intersection Observer API๋ฅผ ์ด์ฉํ์ด์ ํฌ๊ฒ ๊ณต๋ถ๋ฅผ ์ํ์ง๋ง ์ด๋ฒ๊ธฐํ์ ์ ๋๋ก ๊ณต๋ถํด๋ณด๊ณ ์ ํ๋ค.
Debouncing
Debouncing์ ์์ฃผ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ฅผ ์ํ๋ ์์ ์ ์ด๋ฒคํธ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ผ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด๋ค. ์ด๋ฒ๊ณผ์ ์์๋ input์ onChange๋ก ์ธํด ์๊ธฐ๋ ์ด๋ฒคํธ์ ๋ฐ๋ผ api๋ฅผ ํธ์ถํด์ ์ถ์ฒ ๊ฒ์์ด๋ฅผ ๋ฐ์์์ผํ๋ค. ๋ง์ฝ์ ๋ชจ๋ event์ ๋ํด์ api๋ฅผ ๋ถ๋ฌ์จ๋ค๋ฉด ๋ถํ์ํ API ํธ์ถ์ด ์๊ธฐ๊ธฐ ๋๋ฌธ์ onChange์ event ์ค ์ฌ์ฉ์๊ฐ ์ ๋ ฅ์ ๋ง์ณค์ ๋์ input value๋ฅผ api๋ก ํธ์ถํ๋๊ฒ ๋ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ด ๋์๋ค. ์ด๋ฅผ ๊ตฌํํ๊ธฐ ์ํด์ ๋ง์ง๋ง์ผ๋ก ์ ๋ ฅ๋ ๊ฐ์ api๋ก ์ ๋ฌํ๋ debounce๋ฐฉ์์ ์ด์ฉํ๋ค.
useEffect(() => {
if (query) {
if (NO_SESSION_ITEM) {
dispatch({ type: "SET_DATA", data: cachedItem })
} else {
const debounce = setTimeout(() => {
getResponse()
}, DELAY_TIME)
return () => clearTimeout(debounce)
}
}
}, [query])setTimeout์ ์ด์ฉํด delay time ๋ค์ ์ ๋ฌํด์ค callback ํจ์๋ฅผ ์คํํ๊ฒ ํ๋๋ฐ, ์ด๋ input์ ์ ๋ ฅ์ผ๋ก query๊ฐ ๋ฐ๋๋ฉด ์ด์ ์คํ๋๊ธฐ์ค์ด๋ debounce ํจ์๋ clearTimemout์ผ๋ก ์ธํด ์ฌ๋ผ์ง๊ฒ ๋๊ณ ๋ง์ง๋ง์ผ๋ก ์ ๋ฌํด์ค ๊ฐ๋ง ์คํ์ํค๋ ๋ฐฉ์์ผ๋ก debouncing์ ๊ตฌํํ๋ค.
Throttling
Throttling์ ์ผ์ ์๊ฐ์ ๋๊ณ ์๊ฐ ์์ ํ๋์ ์ด๋ฒคํธ๋ง์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด๋ค. debouncing๊ณผ ๋ค๋ฅธ ์ ์ผ๋ก ์ ํด์ง ์๊ฐ๋ด์ ๋ค๋ฅธ ์ ๋ ฅ์ด ๋ค์ด์๋ ๋ฌด์ํ๊ณ ์ ํด์ง ์๊ฐ์ด ์ง๋ ์ดํ์ ๋ค์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์ ์คํ์ํจ๋ค๋ ์ ์ด๋ค. ๋์ ์ ๋ฆฌํ๋ฉด ์ฃผ๊ธฐ์ ์ผ๋ก ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๋ ๊ฒ์ด Throttling, ์ฒ์์ด๋ ๋์ ๋ค์ด์จ ์ด๋ฒคํธ๋ง์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด Debouncing์ผ๋ก ์ดํดํ ์ ์๋ค.
debouncing์ ๊ฒ์๊ณผ ๊ฐ์ด ์์ ์ ๊ฒฐ๊ณผ๊ฐ ์ค์ํ ๋ ์ฌ์ฉํ๊ณ , throttling์ ๋ฌดํ ์คํฌ๋กค๊ณผ ๊ฐ์ ๊ณผ์ ์์ ์ฑ๋ฅ๊ฐ์ ์ ์ํด ์ฌ์ฉํ ์ ์๋ค. ์ด๋ฒ๊ธฐํ์ ์ ๋๋ก ์ดํดํ ์ ์์ด ์ข์ ๊ธฐํ๊ฐ ๋์๋ค.
โจ ํค๋ณด๋๋ง์ผ๋ก ์ถ์ฒ๊ฒ์์ด๋ก ์ด๋๊ฐ๋ฅํ๋๋ก ๊ตฌํ
ํค๋ณด๋๋ง์ผ๋ก ์ถ์ฒ๊ฒ์์ด๋ค๋ก ์ด๋ํ๊ธฐ ์ํด, ์ถ์ฒ ๊ฒ์์ด ๋ฆฌ์คํธ ๋ฐฐ์ด์ index์ keydown, keyup event๋ฅผ ์ด์ฉํ๋ค.
window์ keydown, keyup Event๋ฅผ ์ฌ์ฉํด ํ๋ฉด์์ ๋ฐฉํฅํค๋ฅผ ๋๋ฌ ๋ฐ๋ก ์ถ์ฒ๊ฒ์์ด ๋ฆฌ์คํธ๋ก ์ด๋์ด ๊ฐ๋ฅํ๊ฒ ํด, ๋ณํ๋ index์ searchItem์ index๊ฐ ๊ฐ์๋ ํด๋น searchItem ์ปดํฌ๋ํธ์ ๋ฐฐ๊ฒฝ์์ ๋ณํ์์ผ ํ์ฌ ์์น๋ฅผ UI๋ก ์ ์ ์๊ฒ ํ๋ค.
์ถ์ฒ ๊ฒ์์ด ๋ฆฌ์คํธ ๋ฐฐ์ด์ ์๋ฃ ์์ด ๋ง์์ง๋ฉด scroll์ด ์๊ธฐ๊ณ container ํฌ๊ธฐ ๋ฐ์ item์ด ๋ณด์ด์ง ์๋ ๋ฌธ์ ์ ์ด ์์๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ scrollIntoView ๋ฉ์๋๋ฅผ ์ด์ฉํด ํด๋น ์์ดํ ์ ์์น๋ก ์๋์ผ๋ก ์คํฌ๋กค์ด ๋ ์ ์๊ฒ ํ๋ค.
๋ง์ฐ์ค์ด๋๊ณผ ํค๋ณด๋์ด๋์ด ํธํ์ด ๊ฐ๋ฅํ ์ ์๊ฒ ํ๊ธฐ ์ํด์ mouse Event์ ๋ฐ๋ผ์๋ index๊ฐ ๋ณํ ์ ์๊ฒ ํ๋๋ฐ, ์ฒ์์ mouseMove ์ด๋ฒคํธ๋ฅผ ์ด์ฉํ๋๋ ๋๋ฌด ๋ง์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํด ๋นํฉํ๊ธฐ๋ ํ๋ค. ๋ง์ฐ์ค๊ฐ ํด๋น item์ ์ฌ๋ผ์์ ๋๋ง index๋ฅผ ๋ฐ๊ฟ์ฃผ๋ฉด ๋๋๊น mouseEnter๋ก ํด๊ฒฐํ ์ ์์๋ค.
์ ์ ํ ์ด๋ฒคํธ ์ ํ์ด ์ค์ํ๋ค๋ ๊ฒ๋ ๋ฐฐ์ธ ์ ์๋ ๊ธฐํ์๋ค.
//useKeyboard
import { useState, useEffect } from 'react';
type KeyType = 'ArrowDown' | 'ArrowUp' | 'Enter';
const useKeyPress = (targetKey: KeyType) => {
const [keyPressed, setKeyPressed] = useState(false);
const downHandler = (event: KeyboardEvent) => {
const { key } = event;
if (key === targetKey) {
setKeyPressed(true);
}
};
const upHandler = (event: KeyboardEvent) => {
const { key } = event;
if (key === targetKey) {
setKeyPressed(false);
}
};
useEffect(() => {
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
});
return keyPressed;
};
export { useKeyPress };
//SearchItem.tsx
const KEYWORD_INDEX = 1;
const SearchItem = ({
active
...
}: SearchItemProps) => {
const itemRef = useRef<HTMLLIElement>(null);
const handleMouseEnter = () => {
setIsMovingMouse(true);
setCursor(index);
};
useEffect(() => {
if (active && !isMovingMouse) {
itemRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
});
return (
<Link to={`/search?q=${text}`}>
<S.Wrapper ref={itemRef} active={active} onMouseEnter={handleMouseEnter}>
<BsSearch />
<span>
{textArray?.map((item, idx) => {
if (idx === KEYWORD_INDEX) {
return <b key={item}>{item}</b>;
}
return item;
})}
</span>
</S.Wrapper>
</Link>
);
};
export default SearchItem;๐ ๋ง์น๋ฉฐ
์ด์ ๋ง์ง๋ง ๊ณผ์ ๋ง์ ์์ ๋๊ณ ์๋ค. ๋ค์์ฃผ๋ฉด ํ๋ฌ๊ฐ์ ๊ณผ์ ์ด ๋๋๊ณ ์ง์ง ์ง์์ ํ๊ฒ๋๋๋ฐ ๊ฑฑ์ ๋๊ณ ๋๋ ต๊ธฐ๋ ํ๋ค. ํ์ง๋ง ๋ฐฐ์ด ๋ด์ฉ์ ํตํด ๋ํ์ง ์์ผ์ ๋ค๋ฅธ ์ฌ๋๋ค์ ์ฝ๋๋ฅผ ๋ณด๋ฉด์ ๋ฐฐ์ด ๋ ์ข์ ์ฝ๋๋ค์ ๋ด๊ฐ ํด์๋ ํ๋ก์ ํธ๋ค์ ์ ์ฉํด ๊ฐ๋ค๋ฉด ๋ถ๋ช ๋ ํฐ ๊ฒฝ์๋ ฅ๊ณผ ํ์ด ๋ ๊ฒ ๊ฐ๋ค๋ ์์ ๊ฐ๋ ์๊ธด๋ค. ๊ณต๋ถํ ๊ฑด ๋ง์ง๋ง, ์กฐ๊ธํ ๋ง์๋ณด๋ค ๊ธฐ์ ๋ง์์ผ๋ก ๋ฐฐ์๊ฐ๋ ํ๋๋ฅผ ์ ์งํด๊ฐ์.